#include "stdafx.h"
#include "GameEngine.h"

#include "Box2d/Box2d.h"

#include <iostream>

#include <windows.h>

#include "IGameState.h"
#include "SDLInitializer.h"
#include "Utilities.h"

#include "SDL_syswm.h"
#include "Timer.h"

#include "InputManager.h"
#include "GUISystem.h"
#include "Event.h"
#include "IEventListener.h"

using namespace std;

namespace ZeEngine
{
    GameEngine::GameEngine()
	{
        m_pGUISystem = new GUISystem();
        m_pGameState = NULL;
        m_deltaMS = 0;
        m_fps = 0;
        m_fpsCounter = 0;
        m_started = false;

        //State management
        m_PopStates = 0;

        m_eventListeners.resize(5);
	}

	GameEngine::~GameEngine()
	{
	}

    bool GameEngine::FrameFunc()
    {
        PopStates();

        if (PushStates())
        {
            if (m_pGameState && m_pGUISystem)
            {
                HandleEvents();

                //Add all entities before we start the update
                m_entityManager->PreUpdate();

                m_pGUISystem->PreUpdate(*this);
                m_pGameState->PreUpdate(*this);
                m_pGameState->PreUpdateSystems(*this);

                //We are losing a bit of accuracy by  casting to an int but its not a big deal
                bool fReturn = m_pGUISystem->Update(*this);
                fReturn |= m_pGameState->Update(*this);
                fReturn |= m_pGameState->UpdateSystems(*this);

                m_pGUISystem->PostUpdate(*this);
                m_pGameState->PostUpdate(*this);
                m_pGameState->PostUpdateSystems(*this);

                //Remove all entities
                m_entityManager->PostUpdate();

                return fReturn;
            }

            LOG_ERROR("NULL Game state found");
            return false;
        }

        LOG_ERROR("Error initializing state");
        return false;
    }

    void GameEngine::PopStates()
    {
        while(m_PopStates > 0)
        {
            if (!m_States.empty())
            {
                auto top = m_States.top();
                m_States.pop();
                m_PopStates--;

                //Remove the previous state and clean it up 
                if (top)
                {
                    top->Destroy(*this);
                    top->DestroySystems(*this);
                }

                if (!m_States.empty())
                {
                    m_pGameState = m_States.top();
                    m_pGameState->StateIn();
                }
                else if (m_pNewStates.empty())
                {
                    Logger::getInstance()->logError("All states popped. There is no more game states.");
                    m_pGameState = NULL;
                }
            }
            else
            {
                Logger::getInstance()->logError("Cannot pop state. There is nothing to pop.");
                m_pGameState = NULL;
                m_PopStates = 0;
            }
        }
    }

    bool GameEngine::PushStates()
    {
        bool fContinue = true;

        while (!m_pNewStates.empty())
        {
            if (m_pGameState)
                m_pGameState->StateOut();

            //Set the next game state
            auto pFront = m_pNewStates.front();

            m_States.push(pFront);
            m_pGameState = pFront;

            fContinue &= m_pGameState->Init(*this);
            fContinue &= m_pGameState->InitSystems(*this);

            m_pNewStates.pop();
        }

        return fContinue;
    }

    void GameEngine::AddEvent(unique_ptr<Event> pEvent)
    {
        m_events.push_back(move(pEvent));
    }

    void GameEngine::AddEventListener(IEventListener* pListener)
    {
        while (m_eventListeners.size() <= pListener->GetID())
        {
            m_eventListeners.resize(m_eventListeners.size() * 2);
        }

        m_eventListeners[pListener->GetID()] = pListener;
    }

    void GameEngine::RemoveEventListener(IEventListener* pListener)
    {
        m_eventListeners[pListener->GetID()] = nullptr;
    }

    void GameEngine::HandleEvents()
    {
        for (auto& evt : m_events)
        {
            bool fHandled = false;
            for (auto listener : m_eventListeners)
            {
                if (listener)
                {
                    //If the event is handled move to the next one
                    if (listener->HandleEvent(*evt.get()))
                    {
                        break;
                    }
                }
            }
        }
        m_events.clear();
    }

    void GameEngine::PreRender(SDL_Renderer* pRenderer, SDL_Texture* pRenderTexture)
    {
        if (pRenderer)
        {
            SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 0);    
        
            if (SDL_SetRenderTarget(pRenderer, pRenderTexture ) == -1)
            {
                LOG_ERROR("Error setting target render texture: " + string(SDL_GetError()));
            }
        
            SDL_RenderClear(pRenderer);  
         }
    }

    bool GameEngine::RenderFunc(f32 interpolation)
    {
        if (m_pGameState)
        {       
            PreRender(m_SDLInitializer.GetRenderer(), m_SDLInitializer.GetWindowTexture());
            m_pGameState->RenderSystems(*m_SDLInitializer.GetRenderer(), *this, interpolation);
            m_pGUISystem->Render(*m_SDLInitializer.GetRenderer(), *this, interpolation);
            PostRender(m_SDLInitializer.GetRenderer(), m_SDLInitializer.GetWindowTexture());
        }
        return false;
    }

    void GameEngine::PostRender(SDL_Renderer* pRenderer, SDL_Texture* pRenderTexture)
    {
        SDL_SetRenderTarget( pRenderer, NULL );
        SDL_RenderCopyEx(pRenderer, pRenderTexture, NULL, NULL, 0, NULL, SDL_FLIP_NONE);
        SDL_RenderPresent(pRenderer);
    }

    void GameEngine::PushState(unique_ptr<IGameState> pGameState)
    {
        m_pNewStates.push(move(pGameState));
    }

    void GameEngine::PopState()
    {
        m_PopStates++;
    }

   
	bool GameEngine::Init(unique_ptr<IGameState> pInitialState, const std::string& configFile)
	{
		bool fSuccess = configFile == "";

		if (configFile != "")
		{
			fSuccess = m_Config.LoadConfig(configFile);
		}

		if (fSuccess)
		{	
			fSuccess = InitializeSDL();

			SDL_SysWMinfo SysInfo; //Will hold our Window information
			SDL_VERSION(&SysInfo.version); //Set SDL version

			if (SDL_GetWindowWMInfo(m_SDLInitializer.GetWindow(), &SysInfo) <= 0)
			{
				LOG_ERROR(SDL_GetError());
				fSuccess = false;
			}

            m_pGUISystem->Init(*this);
            m_entityManager = make_unique<EntityManager>(*this);

            PushState(move(pInitialState));
		}

		return fSuccess;
    }

    bool GameEngine::InitializeSDL()
    {
        bool fSuccess = false;

        fSuccess = m_SDLInitializer.InitializeSDL(m_Config);

        if (fSuccess)
        {
            unique_ptr<ResourceLoader> resourceLoader = make_unique<ResourceLoader>(m_SDLInitializer.GetRenderer());
            m_resourceManager.SetResourceLoader(move(resourceLoader));
        }

        return fSuccess;
    }

    bool GameEngine::Start(std::unique_ptr<IGameState> pGameState, const std::string& configFile)
    {
#ifndef _DEBUG
        try
#endif
        {
            if (!m_started)
            {
                if (Init(move(pGameState), configFile))
                {
                    Timer timer;
                    timer.start();

                    u32 nTicksPerSecond = m_Config.GetTicksPerSecond();
                    s32 nMaxFrameSkip = m_Config.GetMaxFrameskip();

                    bool bDontSuspend = true;

                    // MAIN LOOP
                    const int cSkipTicks = 1000 / nTicksPerSecond;
                    u64 nextGameTick = static_cast<u64>(timer.getElapsedTimeInMilliSec());
                    u64 startTickMS = 0;

                    int loops;

                    f32 interpolation = 0.0f;
                    f64 fpsDeltaTickMS = 0;

                    bool bActive = true;
                    bool bLooping = true;
                    bool bForceQuit = false;

                    SDL_Event event;

                    while (bLooping && !bForceQuit)
                    {
                        while (SDL_PollEvent(&event))
                        {
                            if (event.type == SDL_QUIT)
                            {
                                bForceQuit = true;
                            }
                            else
                            {
                                InputManager::HandleEvent(event);
                            }
                        }

                        if (bActive || bDontSuspend)
                        {
                            loops = 0;
                            u64 elapsedTime = static_cast<u64>(timer.getElapsedTimeInMilliSec());

                            while (timer.getElapsedTimeInMilliSec() > nextGameTick && loops < nMaxFrameSkip)
                            {
                                m_deltaMS = cSkipTicks;
                                startTickMS = static_cast<u64>(timer.getElapsedTimeInMilliSec());

                                fpsDeltaTickMS += m_deltaMS;

                                if (fpsDeltaTickMS >= 1000)
                                {
                                    fpsDeltaTickMS = 0;
                                    m_fps = m_fpsCounter;
                                    m_fpsCounter = 0;
                                }

                                bLooping = FrameFunc();

                                nextGameTick += cSkipTicks;
                                loops++;
                            }

                            interpolation = static_cast<float>(timer.getElapsedTimeInMilliSec() + cSkipTicks - nextGameTick) / static_cast<float>(cSkipTicks);
                            m_fpsCounter++;
                            RenderFunc(interpolation);
                        }
                    }

                    Destroy();

                    bActive = false;

                    return true;
                }
            }
        }
#ifndef _DEBUG
       catch (...)
        {
            HandleException(current_exception());
        }
#endif
        return false;
    }

    void GameEngine::HandleException(exception_ptr eptr)
    {
        try
        {
            rethrow_exception(eptr);
        }
        catch (const exception& e)
        {
            LOG_ERROR(e.what());
        }

    }

    /*
    void GameEngine::InitBox2D()
    {
        b2Vec2 gravity(0, 9.8);
        m_pWorld = new b2World(gravity);
    }
    */

    void GameEngine::Destroy()
    {             
        if (m_pGameState)
        {
            m_pGUISystem->Destroy(*this);
            m_pGameState->Destroy(*this);
            m_pGameState->DestroySystems(*this);
        }

        m_SDLInitializer.DestroySDL();
    }

    u32 GameEngine::GetDeltaMS()
    { 
        return m_deltaMS; 
    }

    float GameEngine::GetDeltaSeconds()
    {
        return m_deltaMS / 1000.0f;
    }
}
